package top.cardone.data.jdbc.dao.impl;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.util.CollectionUtils;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.core.util.func.Func1;
import top.cardone.data.jdbc.support.NamedParameterJdbcOperationsSupport;

import java.util.List;
import java.util.Map;

/**
 * Created by yao hai tao on 2015/8/26.
 */
@Log4j2
public class CrudDaoImpl implements top.cardone.data.dao.CrudDao {
	@lombok.Setter
	protected Table<String, String, String> configTable = HashBasedTable.create();

	@Override
	public int delete(Object delete) {
		String sqlFilePath = this.getSqlFilePath("delete");

		Map<String, Object> deleteMap = this.toMap(delete, "delete");

		Map<String, Object> newDeleteMap = Maps.newHashMap();

		for (Map.Entry<String, Object> deleteEntry : deleteMap.entrySet()) {
			newDeleteMap.put("where_and_eq_" + deleteEntry.getKey(), Boolean.TRUE.toString());
			newDeleteMap.put("where_and_eq_" + deleteEntry.getKey() + "_value", deleteEntry.getValue());
		}

		return this.update(sqlFilePath, newDeleteMap);
	}

	@Override
	public int deleteAll() {
		String sqlFilePath = this.getSqlFilePath("delete");

		Map<String, Object> newDeleteMap = Maps.newHashMap();

		newDeleteMap.put("delete_all", Boolean.TRUE.toString());

		return this.update(sqlFilePath, newDeleteMap);
	}

	@Override
	public int deleteByIds(Object ids) {
		String sqlFilePath = this.getSqlFilePath("deleteByIds");

		return this.update(sqlFilePath, this.toMap(ids, "ids"));
	}

	@Override
	public int[] deleteList(List<Object> deleteList) {
		if (CollectionUtils.isEmpty(deleteList)) {
			return new int[0];
		}

		String sqlFilePath = this.getSqlFilePath("delete");

		List<Map<String, Object>> newDeleteListMap = Lists.newArrayList();

		for (Object delete : deleteList) {
			Map<String, Object> deleteMap = this.toMap(delete, "delete");

			Map<String, Object> newDeleteMap = Maps.newHashMap();

			for (Map.Entry<String, Object> deleteEntry : deleteMap.entrySet()) {
				newDeleteMap.put("where_and_eq_" + deleteEntry.getKey(), Boolean.TRUE.toString());
				newDeleteMap.put("where_and_eq_" + deleteEntry.getKey() + "_value", deleteEntry.getValue());
			}

			newDeleteListMap.add(newDeleteMap);
		}

		return this.updateList(sqlFilePath, newDeleteListMap);
	}

	@Override
	public List<Map<String, Object>> findList(Object findList) {
		String sqlFilePath = this.getSqlFilePath("find");

		Map<String, Object> findListMap = this.toMap(findList, "find");

		Map<String, Object> newFindListMap = Maps.newHashMap();

		for (Map.Entry<String, Object> findListEntry : findListMap.entrySet()) {
			newFindListMap.put("where_and_eq_" + findListEntry.getKey(), Boolean.TRUE.toString());
			newFindListMap.put("where_and_eq_" + findListEntry.getKey() + "_value", findListEntry.getValue());
		}

		newFindListMap.putAll(findListMap);

		return this.findList(sqlFilePath, newFindListMap);
	}

	protected List<Map<String, Object>> findList(String sqlFilePath) {
		return this.findList(sqlFilePath, null);
	}

	protected List<Map<String, Object>> findList(String sqlFilePath, Map<String, ?> findList) {
		String parseBeanId = this.getParseBeanId();
		String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

		return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, findList, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.queryForList(sql, findList));
	}

	@Override
	public Map<String, Object> findOne(Object findOne) {
		String sqlFilePath = this.getSqlFilePath("find");

		Map<String, Object> findOneMap = this.toMap(findOne, "find");

		Map<String, Object> newFindOneMap = Maps.newHashMap();

		for (Map.Entry<String, Object> findOneEntry : findOneMap.entrySet()) {
			newFindOneMap.put("where_and_eq_" + findOneEntry.getKey(), Boolean.TRUE.toString());
			newFindOneMap.put("where_and_eq_" + findOneEntry.getKey() + "_value", findOneEntry.getValue());
		}

		return this.findOne(sqlFilePath, newFindOneMap);
	}

	protected Map<String, Object> findOne(String sqlFilePath, Map<String, ?> findOne) {
		List<Map<String, Object>> findList = this.findList(sqlFilePath, findOne);

		if (CollectionUtils.isEmpty(findList)) {
			return null;
		}

		if (findList.size() > 1) {
			log.error("单条数据查询方法，查询出" + findList.size() + "条数据:sqlFilePath=" + sqlFilePath + ", findOne=" + findOne);
		}

		return findList.get(0);
	}

	protected String[] getCodes() {
		return StringUtils.split(StringUtils.remove(configTable.get("config", "codes"), " "), ",");
	}

	protected String getNamedParameterJdbcOperationsBeanId() {
		return configTable.get("config", "namedParameterJdbcOperationsBeanId");
	}

	protected String getParseBeanId() {
		return configTable.get("config", "parseBeanId");
	}

	protected String[] getPks() {
		String[] pks = StringUtils.split(StringUtils.remove(configTable.get("config", "pks"), " "), ",");

		if (ArrayUtils.isNotEmpty(pks)) {
			return pks;
		}

		return this.getCodes();
	}

	protected String[] getWheres(Map<String, Object> objectMap) {
		String[] pks = this.getPks();

		String[] codes = this.getCodes();

		if (ArrayUtils.isEmpty(codes)) {
			return pks;
		}

		for (String pk : pks) {
			Object val = objectMap.get(pk);

			if (val == null || (val instanceof String && StringUtils.isBlank(val.toString()))) {
				return codes;
			}
		}

		return pks;
	}

	protected String getSqlFilePath(String sqlFilePath) {
		if (StringUtils.startsWith(sqlFilePath, "/")) {
			return sqlFilePath;
		}

		String sqlFileRoot = configTable.get("config", "sqlFileRoot");

		String sqlFileSuffix = StringUtils.defaultIfBlank(configTable.get("config", "sqlFileSuffix"), ".ftl");

		return sqlFileRoot + sqlFilePath + sqlFileSuffix;
	}

	@Override
	public int insert(Object insert) {
		String sqlFilePath = this.getSqlFilePath("insert");

		Map<String, Object> insertMap = this.toMap(insert, "insert");

		Map<String, Object> newInsertMap = Maps.newHashMap();

		for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
			newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
			newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
		}

		return this.update(sqlFilePath, newInsertMap);
	}

	@Override
	public int insertByNotExists(Object insert) {
		String sqlFilePath = this.getSqlFilePath("insertByNotExists");

		Map<String, Object> insertMap = this.toMap(insert, "insert");

		Map<String, Object> newInsertMap = Maps.newHashMap();

		String[] codes = this.getCodes();

		for (String code : codes) {
			newInsertMap.put("where_and_eq_" + code, Boolean.TRUE.toString());
			newInsertMap.put("where_and_eq_" + code + "_value", insertMap.get(code));
		}

		for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
			newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
			newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
		}

		return this.update(sqlFilePath, newInsertMap);
	}

	@Override
	public int[] insertList(List<Object> insertList) {
		if (CollectionUtils.isEmpty(insertList)) {
			return new int[0];
		}

		String sqlFilePath = this.getSqlFilePath("insert");

		List<Map<String, Object>> newInsertListMap = Lists.newArrayList();

		for (Object insert : insertList) {
			Map<String, Object> insertMap = this.toMap(insert, "insert");

			Map<String, Object> newInsertMap = Maps.newHashMap();

			for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
				newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
				newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
			}

			newInsertListMap.add(newInsertMap);
		}

		return this.updateList(sqlFilePath, newInsertListMap);
	}

	@Override
	public int[] insertListByNotExists(List<Object> insertList) {
		if (CollectionUtils.isEmpty(insertList)) {
			return new int[0];
		}

		String sqlFilePath = this.getSqlFilePath("insertByNotExists");

		List<Map<String, Object>> newInsertListMap = Lists.newArrayList();

		String[] codes = this.getCodes();

		for (Object insert : insertList) {
			Map<String, Object> insertMap = this.toMap(insert, "insert");

			Map<String, Object> newInsertMap = Maps.newHashMap();

			for (String code : codes) {
				newInsertMap.put("where_and_eq_" + code, Boolean.TRUE.toString());
				newInsertMap.put("where_and_eq_" + code + "_value", insertMap.get(code));
			}

			for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
				newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
				newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
			}

			newInsertListMap.add(newInsertMap);
		}

		return this.updateList(sqlFilePath, newInsertListMap);
	}

	@Override
	public List<Object> readList(Object readList) {
		String sqlFilePath = this.getSqlFilePath("read");

		Map<String, Object> readListMap = this.toMap(readList, "read");

		Map<String, Object> newReadListMap = Maps.newHashMap();

		for (Map.Entry<String, Object> readListEntry : readListMap.entrySet()) {
			newReadListMap.put("where_and_eq_" + readListEntry.getKey(), Boolean.TRUE.toString());
			newReadListMap.put("where_and_eq_" + readListEntry.getKey() + "_value", readListEntry.getValue());
		}

		newReadListMap.put("object_id", readListMap.get("object_id"));

		return this.readList(sqlFilePath, newReadListMap);
	}

	protected List<Object> readList(String sqlFilePath, Map<String, ?> readList) {
		String parseBeanId = this.getParseBeanId();
		String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

		return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, readList, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.queryForList(sql, readList, Object.class));
	}

	@Override
	public Object readOne(Object readOne) {
		String sqlFilePath = this.getSqlFilePath("read");

		Map<String, Object> readOneMap = this.toMap(readOne, "read");

		Map<String, Object> newReadOneMap = Maps.newHashMap();

		for (Map.Entry<String, Object> readOneEntry : readOneMap.entrySet()) {
			if ("object_id".equals(readOneEntry.getKey())) {
				continue;
			}

			newReadOneMap.put("where_and_eq_" + readOneEntry.getKey(), Boolean.TRUE.toString());
			newReadOneMap.put("where_and_eq_" + readOneEntry.getKey() + "_value", readOneEntry.getValue());
		}

		newReadOneMap.put("object_id", readOneMap.get("object_id"));

		return this.readOne(sqlFilePath, newReadOneMap);
	}

	protected Object readOne(String sqlFilePath, Map<String, ?> readOne) {
		List<Object> readList = this.readList(sqlFilePath, readOne);

		if (CollectionUtils.isEmpty(readList)) {
			return null;
		}

		if (readList.size() > 1) {
			log.error("单条数据查询方法，查询出" + readList.size() + "条数据:sqlFilePath=" + sqlFilePath + ", readOne=" + readOne);
		}

		return readList.get(0);
	}

	@Override
	public int save(Object save) {
		return this.save(null, null, save);
	}

	protected int save(String updateSqlFilePath, String insertSqlFilePath, Object save) {
		int updateCount;

		if (StringUtils.isBlank(updateSqlFilePath)) {
			updateCount = this.update(save);
		} else {
			Map<String, Object> updateMap = this.toMap(save, "update");

			updateCount = this.update(updateSqlFilePath, updateMap);
		}

		if (updateCount > 0) {
			return updateCount;
		}

		if (StringUtils.isBlank(insertSqlFilePath)) {
			return this.insertByNotExists(save);
		} else {
			Map<String, Object> insertMap = this.toMap(save, "insert");

			return this.update(insertSqlFilePath, insertMap);
		}
	}

	public void setConfigTable(Map<String, Map<String, String>> table) {
		if (MapUtils.isEmpty(table)) {
			return;
		}

		for (Map.Entry<String, Map<String, String>> tableEntry : table.entrySet()) {
			for (Map.Entry<String, String> rowEntry : tableEntry.getValue().entrySet()) {
				configTable.put(tableEntry.getKey(), rowEntry.getKey(), rowEntry.getValue());
			}
		}
	}

	protected Map<String, Object> toMap(Object obj, String key) {
		Map<String, Object> newMap = top.cardone.context.util.MapUtils.toMap(obj, key);

		Map<String, String> row = configTable.row(StringUtils.defaultString(key, "update"));

		if (MapUtils.isEmpty(row)) {
			return newMap;
		}

		for (Map.Entry<String, String> rowEntry : row.entrySet()) {
			Object value = newMap.get(rowEntry.getKey());

			if (value == null || ((value instanceof String) && StringUtils.isBlank((String) value))) {
				newMap.put(rowEntry.getKey(), ApplicationContextHolder.func(Func1.class, func -> func.func(newMap), rowEntry.getValue()));
			}
		}

		return newMap;
	}

	@Override
	public int update(Object update) {
		String sqlFilePath = this.getSqlFilePath("update");

		Map<String, Object> updateMap = this.toMap(update, "update");

		Map<String, Object> newUpdateMap = Maps.newHashMap();

		String[] wheres = this.getWheres(updateMap);

		for (String where : wheres) {
			newUpdateMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
			newUpdateMap.put("where_and_eq_" + where + "_value", updateMap.get(where));
		}

		for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
			if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
				continue;
			}

			newUpdateMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
			newUpdateMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
		}

		return this.update(sqlFilePath, newUpdateMap);
	}

	protected int update(String sqlFilePath, Map<String, ?> update) {
		String parseBeanId = this.getParseBeanId();
		String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

		return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, update, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.update(sql, update));
	}

	@Override
	public int[] updateList(List<Object> updateList) {
		if (CollectionUtils.isEmpty(updateList)) {
			return new int[0];
		}

		String sqlFilePath = this.getSqlFilePath("update");

		List<Map<String, Object>> newUpdateListMap = Lists.newArrayList();

		for (Object update : updateList) {
			Map<String, Object> updateMap = this.toMap(update, "update");

			Map<String, Object> newUpdateMap = Maps.newHashMap();

			String[] wheres = this.getWheres(updateMap);

			for (String where : wheres) {
				newUpdateMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
				newUpdateMap.put("where_and_eq_" + where + "_value", updateMap.get(where));
			}

			for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
				if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
					continue;
				}

				newUpdateMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
				newUpdateMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
			}

			newUpdateListMap.add(newUpdateMap);
		}

		return this.updateList(sqlFilePath, newUpdateListMap);
	}

	@Override
	public int[][] saveList(List<Object> saveList) {
		int[] updateListCount = this.updateList(saveList);

		List<Object> insertList = Lists.newArrayList();

		for (int i = 0; i < updateListCount.length; i++) {
			if (updateListCount[i] < 1) {
				insertList.add(saveList.get(i));
			}
		}

		int[] insertListCount = this.insertListByNotExists(insertList);

		return new int[][]{updateListCount, insertListCount};
	}

	protected int[] updateList(String sqlFilePath, List<Map<String, Object>> updateList) {
		int[] updateCounts = new int[updateList.size()];

		for (int i = 0; i < updateList.size(); i++) {
			updateCounts[i] = this.update(sqlFilePath, updateList.get(i));
		}

		return updateCounts;
	}

	protected int[] updateListForStaticSql(String sqlFilePath, List<Map<String, Object>> updateList) {
		String parseBeanId = this.getParseBeanId();
		String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

		final SqlParameterSource[] batchArgs = new SqlParameterSource[updateList.size()];

		for (int i = 0; i < updateList.size(); i++) {
			batchArgs[i] = new MapSqlParameterSource(updateList.get(i));
		}

		return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, updateList.get(0), namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.batchUpdate(sql, batchArgs));
	}
}